home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
hity wydania
/
Ubuntu 9.10 PL
/
karmelkowy-koliberek-desktop-9.10-i386-PL.iso
/
casper
/
filesystem.squashfs
/
usr
/
sbin
/
update-binfmts
< prev
next >
Wrap
Text File
|
2009-09-19
|
20KB
|
733 lines
#! /usr/bin/perl -w
# Copyright (c) 2000, 2001, 2002 Colin Watson <cjwatson@debian.org>.
# See update-binfmts(8) for documentation.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
use strict;
use POSIX qw(uname);
use Text::Wrap;
use Binfmt::Lib qw($admindir $importdir $procdir $auxdir $cachedir quit warning);
use Binfmt::Format;
my $VERSION = '1.2.14';
$Text::Wrap::columns = 79;
use vars qw($test);
my $register = "$procdir/register";
my $status = "$procdir/status";
my $run_detectors = "$auxdir/run-detectors";
my %formats;
# Various "print something and exit" routines.
sub version ()
{
print "update-binfmts $VERSION.\n"
or die "unable to write version message: $!";
}
sub usage ()
{
version;
print <<EOF
Copyright (c) 2000, 2001, 2002 Colin Watson. This is free software; see
the GNU General Public License version 2 or later for copying conditions.
Usage:
update-binfmts [options] --install <name> <path> <spec>
update-binfmts [options] --remove <name> <path>
update-binfmts [options] --import [<name>]
update-binfmts [options] --display [<name>]
update-binfmts [options] --enable [<name>]
update-binfmts [options] --disable [<name>]
where <spec> is one of:
--magic <byte-sequence> [--mask <byte-sequence>] [--offset <offset>]
--extension <extension>
The following argument may be added to any <spec> to have a userspace
process determine whether the file should be handled:
--detector <path>
Options:
--package <package-name> for --install and --remove, specify the
current package name
--admindir <directory> use <directory> instead of /var/lib/binfmts
as administration directory
--importdir <directory> use <directory> instead of /usr/share/binfmts
as import directory
--cachedir <directory> use <directory> instead of /var/cache/binfmts
as cache directory
--test don't do anything, just demonstrate
--help print this help screen and exit
--version output version and exit
EOF
or die "unable to write usage message: $!";
}
sub usage_quit ($;@)
{
my $me = $0;
$me =~ s#.*/##;
print STDERR wrap '', '', "$me:", @_, "\n";
usage;
exit 2;
}
sub check_supported_os ()
{
my $sysname = (uname)[0];
return if $sysname eq 'Linux';
print <<EOF;
Sorry, update-binfmts currently only works on Linux.
EOF
if ($sysname eq 'GNU') {
print <<EOF;
Patches for Hurd support are welcomed; they should not be difficult.
EOF
}
exit 2;
}
# Make sure options are unambiguous.
sub check_modes ($$)
{
return unless $_[0];
usage_quit "two modes given: --$_[0] and $_[1]";
}
sub check_types ($$)
{
return unless $_[0];
usage_quit "two binary format specifications given: --$_[0] and $_[1]";
}
sub rename_mv ($$)
{
my ($source, $dest) = @_;
return (rename($source, $dest) || (system('mv', $source, $dest) == 0));
}
sub get_import ($)
{
my $name = shift;
my %import;
local *IMPORT;
unless (open IMPORT, "< $name") {
warning "unable to open $name: $!";
return;
}
local $_;
while (<IMPORT>) {
chomp;
my ($name, $value) = split ' ', $_, 2;
$import{lc $name} = $value;
}
close IMPORT;
return %import;
}
# Loading and unloading logic, which should cope with the various ways this
# has been implemented.
sub get_binfmt_style ()
{
my $style;
local *FS;
unless (open FS, '/proc/filesystems') {
# Weird. Assume procfs.
warning "unable to open /proc/filesystems: $!";
return 'procfs';
}
if (grep m/\bbinfmt_misc\b/, <FS>) {
# As of 2.4.3, the official Linux kernel still uses the original
# interface, but Alan Cox's patches add a binfmt_misc filesystem
# type which needs to be mounted separately. This may get into the
# official kernel in the future, so support both.
$style = 'filesystem';
} else {
# The traditional interface.
$style = 'procfs';
}
close FS;
return $style;
}
sub load_binfmt_misc ()
{
if ($test) {
print "load binfmt_misc\n";
return 1;
}
my $style = get_binfmt_style;
# If the style is 'filesystem', then we must already have the module
# loaded, as binfmt_misc wouldn't show up in /proc/filesystems
# otherwise.
if ($style eq 'procfs' and not -f $register) {
if (not -x '/sbin/modprobe' or
system qw(/sbin/modprobe -q binfmt_misc)) {
warning "Couldn't load the binfmt_misc module.";
return 0;
}
}
unless (-d $procdir) {
warning "binfmt_misc module seemed to be loaded, but no $procdir",
"directory! Giving up.";
return 0;
}
# Find out what the style looks like now.
$style = get_binfmt_style;
if ($style eq 'filesystem' and not -f $register) {
if (system ('/bin/mount', '-t', 'binfmt_misc',
'-o', 'nodev,noexec,nosuid', 'binfmt_misc', $procdir)) {
warning "Couldn't mount the binfmt_misc filesystem on $procdir.";
return 0;
}
}
if (-f $register) {
local *STATUS;
if (open STATUS, "> $status") {
print STATUS "1\n";
close STATUS;
} else {
warning "unable to open $status for writing: $!";
}
return 1;
} else {
warning "binfmt_misc initialized, but $register missing! Giving up.";
return 0;
}
}
sub unload_binfmt_misc ()
{
my $style = get_binfmt_style;
if ($test) {
print "unload binfmt_misc ($style)\n";
return 1;
}
if ($style eq 'filesystem') {
if (system '/bin/umount', $procdir) {
warning "Couldn't unmount the binfmt_misc filesystem from",
"$procdir.";
return 0;
}
}
# We used to try to unload the kernel module as well, but it seems that
# it doesn't always unload properly (http://bugs.debian.org/155570) and
# in any case it means that strictly speaking we have to remember if the
# module was loaded when we started. Since it's not actually important,
# we now just don't bother.
return 1;
}
# Actions.
# Enable a binary format in the kernel.
sub act_enable (;$);
sub act_enable (;$)
{
my $name = shift;
if (defined $name) {
my $cacheonly = 0;
$cacheonly = 1 unless load_binfmt_misc;
$cacheonly = 1 if -e "$procdir/$name";
unless ($test or exists $formats{$name}) {
warning "$name not in database of installed binary formats.";
return 0;
}
my $binfmt = $formats{$name};
my $type = ($binfmt->{type} eq 'magic') ? 'M' : 'E';
my $need_detector = (defined $binfmt->{detector} and
length $binfmt->{detector}) ? 1 : 0;
unless ($need_detector) {
# Scan the format database to see if anything else uses the same
# spec as us. If so, assume that we need a detector, effectively
# /bin/true. Don't actually set $binfmt->{detector} though,
# since run-detectors optimizes the case of empty detectors and
# "runs" them last.
for my $id (keys %formats) {
next if $id eq $name;
if ($binfmt->equals ($formats{$id})) {
$need_detector = 1;
last;
}
}
}
# Fake the interpreter if we need a userspace detector program.
my $interpreter = $need_detector ? $run_detectors
: $binfmt->{interpreter};
my $regstring = ":$name:$type:$binfmt->{offset}:$binfmt->{magic}" .
":$binfmt->{mask}:$interpreter:\n";
if ($test) {
print "enable $name with the following format string:\n",
" $regstring";
} else {
local *CACHE;
if (open CACHE, ">$cachedir/$name") {
print CACHE $regstring;
close CACHE or warning "unable to close $cachedir/$name: $!";
} else {
warning "unable to open $cachedir/$name for writing: $!";
}
unless ($cacheonly) {
local *REGISTER;
unless (open REGISTER, ">$register") {
warning "unable to open $register for writing: $!";
return 0;
}
print REGISTER $regstring;
unless (close REGISTER) {
warning "unable to close $register: $!";
return 0;
}
}
}
return 1;
} else {
my $worked = 1;
for my $id (keys %formats) {
$worked &= act_enable $id;
}
return $worked;
}
}
# Disable a binary format in the kernel.
sub act_disable (;$);
sub act_disable (;$)
{
my $name = shift;
return 1 unless -d $procdir; # We're disabling anyway, so we don't mind
if (defined $name) {
unless (-e "$procdir/$name") {
# Don't warn in this circumstance, as it could happen e.g. when
# binfmt-support and a package depending on it are upgraded at
# the same time, so we get called when stopped. Just pretend
# that the disable operation succeeded.
return 1;
}
# We used to check the entry in $procdir to make sure we were
# removing an entry with the same interpreter, but this is bad; it
# makes things really difficult for packages that want to change
# their interpreter, for instance. Now we unconditionally remove and
# rely on the calling logic to check that the entry in $admindir
# belongs to the same package.
#
# In other words, $admindir becomes the canonical reference, not
# $procdir. This is in line with similar update-* tools in Debian.
if ($test) {
print "disable $name\n";
} else {
local *PROCENTRY;
unless (open PROCENTRY, ">$procdir/$name") {
warning "unable to open $procdir/$name for writing: $!";
return 0;
}
print PROCENTRY -1;
unless (close PROCENTRY) {
warning "unable to close $procdir/$name: $!";
return 0;
}
if (-e "$procdir/$name") {
warning "removal of $procdir/$name ignored by kernel!";
return 0;
}
}
return 1;
}
else
{
my $worked = 1;
for my $id (keys %formats) {
if (-e "$procdir/$id") {
$worked &= act_disable $id;
}
}
unload_binfmt_misc; # ignore errors here
return $worked;
}
}
# Install a binary format into binfmt-support's database. Attempt to enable
# the new format in the kernel as well.
sub act_install ($$)
{
my $name = shift;
my $binfmt = shift;
if (exists $formats{$name}) {
# For now we just silently zap any old versions with the same
# package name (has to be silent or upgrades are annoying). Maybe we
# should be more careful in the future.
my $package = $binfmt->{package};
my $old_package = $formats{$name}{package};
unless ($package eq $old_package) {
$package = '<local>' if $package eq ':';
$old_package = '<local>' if $old_package eq ':';
warning "current package is $package, but binary format already",
"installed by $old_package";
return 0;
}
unless (act_disable $name) {
warning "unable to disable binary format $name";
return 0;
}
}
if (-e "$procdir/$name" and not $test) {
# This is a bit tricky. If we get here, then the kernel knows about
# a format we don't. Either somebody has used binfmt_misc directly,
# or update-binfmts did something wrong. For now we do nothing;
# disabling and re-enabling all binary formats will fix this anyway.
# There may be a --force option in the future to help with problems
# like this.
#
# Disabled for --test, because otherwise it never works; the
# vagaries of binfmt_misc mean that it isn't really possible to find
# out from userspace exactly what's going to happen if people have
# been bypassing update-binfmts.
warning "found manually created entry for $name in $procdir;",
"leaving it alone";
return 1;
}
if ($test) {
print "install the following binary format description:\n";
$binfmt->dump_stdout;
} else {
$binfmt->write ("$admindir/$name.tmp") or return 0;
unless (rename_mv "$admindir/$name.tmp", "$admindir/$name") {
warning "unable to install $admindir/$name.tmp as",
"$admindir/$name: $!";
return 0;
}
}
$formats{$name} = $binfmt;
unless (act_enable $name) {
warning "unable to enable binary format $name";
return 0;
}
return 1;
}
# Remove a binary format from binfmt-support's database. Attempt to disable
# the format in the kernel first.
sub act_remove ($$)
{
my $name = shift;
my $package = shift;
unless (exists $formats{$name}) {
# There may be a --force option in the future to allow entries like
# this to be removed; either they were created manually or
# update-binfmts was broken.
warning "$admindir/$name does not exist; nothing to do!";
return 1;
}
my $old_package = $formats{$name}{package};
unless ($package eq $old_package) {
$package = '<local>' if $package eq ':';
$old_package = '<local>' if $old_package eq ':';
warning "current package is $package, but binary format already",
"installed by $old_package; not removing.";
# I don't think this should be fatal.
return 1;
}
unless (act_disable $name) {
warning "unable to disable binary format $name";
return 0;
}
if ($test) {
print "remove $admindir/$name\n";
} else {
unless (unlink "$admindir/$name") {
warning "unable to remove $admindir/$name: $!";
return 0;
}
delete $formats{$name};
unlink "$cachedir/$name";
}
return 1;
}
# Import a new format file into binfmt-support's database. This is intended
# for use by packaging systems.
sub act_import (;$);
sub act_import (;$)
{
my $name = shift;
if (defined $name) {
my $id;
if ($name =~ m!.*/(.*)!) {
$id = $1;
} else {
$id = $name;
$name = "$importdir/$name";
}
if ($id =~ /^(\.\.?|register|status)$/) {
warning "binary format name '$id' is reserved";
return 0;
}
my %import = get_import $name;
unless (scalar keys %import) {
warning "couldn't find information about '$id' to import";
return 0;
}
if (exists $formats{$id}) {
if ($formats{$id}{package} eq ':') {
# Installed version was installed manually, so don't import
# over it.
warning "preserving local changes to $id";
return 1;
} else {
# Installed version was installed by a package, so it should
# be OK to replace it.
}
}
# TODO: This duplicates the verification code below slightly.
unless (defined $import{package}) {
warning "$name: required 'package' line missing";
return 0;
}
unless (-x $import{interpreter}) {
warning "$name: no executable $import{interpreter} found, but",
"continuing anyway as you request";
}
act_install $id, Binfmt::Format->new ($name, %import);
return 1;
} else {
local *IMPORTDIR;
unless (opendir IMPORTDIR, $importdir) {
warning "unable to open $importdir: $!";
return 0;
}
my $worked = 1;
for (readdir IMPORTDIR) {
next unless -f "$importdir/$_";
if (-f "$importdir/$_") {
$worked &= act_import $_;
}
}
closedir IMPORTDIR;
return $worked;
}
}
# Display a format stored in binfmt-support's database.
sub act_display (;$);
sub act_display (;$)
{
my $name = shift;
if (defined $name) {
print "$name (", (-e "$procdir/$name" ? 'enabled' : 'disabled'),
"):\n";
my $binfmt = $formats{$name};
my $package = $binfmt->{package} eq ':' ? '<local>'
: $binfmt->{package};
print <<EOF;
package = $package
type = $binfmt->{type}
offset = $binfmt->{offset}
magic = $binfmt->{magic}
mask = $binfmt->{mask}
interpreter = $binfmt->{interpreter}
detector = $binfmt->{detector}
EOF
} else {
for my $id (keys %formats) {
act_display $id;
}
}
return 1;
}
# Now go.
check_supported_os;
my @modes = qw(install remove import display enable disable);
my @types = qw(magic extension);
my ($package, $name);
my ($mode, $type);
my %spec;
my %unique_options = (
'package' => \$package,
'mask' => \$spec{mask},
'offset' => \$spec{offset},
'detector' => \$spec{detector},
);
my %arguments = (
'admindir' => ['path' => \$admindir],
'importdir' => ['path' => \$importdir],
'cachedir' => ['path' => \$cachedir],
'install' => ['name' => \$name, 'path' => \$spec{interpreter}],
'remove' => ['name' => \$name, 'path' => \$spec{interpreter}],
'package' => ['package-name' => \$package],
'magic' => ['byte-sequence' => \$spec{magic}],
'extension' => ['extension' => \$spec{extension}],
'mask' => ['byte-sequence' => \$spec{mask}],
'offset' => ['offset' => \$spec{offset}],
'detector' => ['path' => \$spec{detector}],
);
my %parser = (
'help' => sub { usage; exit 0; },
'version' => sub { version; exit 0; },
'test' => sub { $test = 1; },
'install' => sub {
-x $spec{interpreter}
or warning "no executable $spec{interpreter} found, but",
"continuing anyway as you request";
},
'remove' => sub {
-x $spec{interpreter}
or warning "no executable $spec{interpreter} found, but",
"continuing anyway as you request";
},
'import' => sub { $name = (@ARGV >= 1) ? shift @ARGV : undef; },
'display' => sub { $name = (@ARGV >= 1) ? shift @ARGV : undef; },
'enable' => sub { $name = (@ARGV >= 1) ? shift @ARGV : undef; },
'disable' => sub { $name = (@ARGV >= 1) ? shift @ARGV : undef; },
'offset' => sub {
$spec{offset} =~ /^\d+$/
or usage_quit 'offset must be a whole number';
},
'detector' => sub {
-x $spec{detector}
or warning "no executable $spec{detector} found, but",
"continuing anyway as you request";
},
);
while (defined($_ = shift))
{
last if /^--$/;
if (!/^--(.+)$/) {
usage_quit "unknown argument '$_'";
}
my $option = $1;
my $is_mode = grep { $_ eq $option } @modes;
my $is_type = grep { $_ eq $option } @types;
my $has_args = exists $arguments{$option};
unless ($is_mode or $is_type or $has_args or exists $parser{$option}) {
usage_quit "unknown argument '$_'";
}
check_modes $mode, $option if $is_mode;
check_types $type, $option if $is_type;
if (exists $unique_options{$option} and
defined ${$unique_options{$option}}) {
usage_quit "more than one --$option option given";
}
if ($has_args) {
my (@descs, @varrefs);
# Split into descriptions and variable references.
my $alt = 0;
foreach my $arg (@{$arguments{$option}}) {
if (($alt = !$alt)) { push @descs, "<$arg>"; }
else { push @varrefs, $arg; }
}
usage_quit "--$option needs @descs" unless @ARGV >= @descs;
foreach my $varref (@varrefs) { $$varref = shift @ARGV; }
}
&{$parser{$option}} if defined $parser{$option};
$mode = $option if $is_mode;
$type = $option if $is_type;
}
$package = ':' unless defined $package;
unless (defined $mode) {
usage_quit 'you must use one of --install, --remove, --import, --display,',
'--enable, --disable';
}
my $binfmt;
if ($mode eq 'install') {
defined $type or usage_quit '--install requires a <spec> option';
if ($name =~ /^(\.\.?|register|status)$/) {
usage_quit "binary format name '$name' is reserved";
}
$binfmt = Binfmt::Format->new ($name, package => $package, type => $type,
%spec);
}
local *ADMINDIR;
unless (opendir ADMINDIR, $admindir) {
quit "unable to open $admindir: $!";
}
for my $name (readdir ADMINDIR) {
if (-f "$admindir/$name") {
my $format = Binfmt::Format->load ($name, "$admindir/$name");
$formats{$name} = $format if defined $format;
}
}
closedir ADMINDIR;
my %actions = (
'install' => [\&act_install, $binfmt],
'remove' => [\&act_remove, $package],
'import' => [\&act_import],
'display' => [\&act_display],
'enable' => [\&act_enable],
'disable' => [\&act_disable],
);
unless (exists $actions{$mode}) {
usage_quit "unknown mode: $mode";
}
my @actargs = @{$actions{$mode}};
my $actsub = shift @actargs;
if ($actsub->($name, @actargs)) {
exit 0;
} else {
quit 'exiting due to previous errors';
}